package com.legstar.talend.xsd2xml;

import java.io.IOException;

import javax.xml.namespace.QName;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.ws.commons.schema.XmlSchema;
import org.apache.ws.commons.schema.XmlSchemaAnnotation;
import org.apache.ws.commons.schema.XmlSchemaAppInfo;
import org.apache.ws.commons.schema.XmlSchemaComplexType;
import org.apache.ws.commons.schema.XmlSchemaElement;
import org.apache.ws.commons.schema.XmlSchemaSimpleType;
import org.apache.ws.commons.schema.XmlSchemaSimpleTypeRestriction;
import org.apache.ws.commons.schema.constants.Constants;
import org.w3c.dom.Element;
import org.w3c.dom.Node;

import com.legstar.coxb.CobolMarkup;
import com.legstar.xsd.XsdMappingException;

/**
 * Produces a TOS Schema using a COBOL-annotated XML schema.
 * <p/>
 * Meant to work with an <code>Cob2XsdSchemaNavigator</code> which guarantees
 * that items are visited in the correct order.
 * <p/>
 * Extracts the COBOL annotations and create TOS schema elements.
 */
public class Cob2TosSchemaProcessor {

    /** All output goes to the TOS schema. */
    private TosSchema _tosSchema = new TosSchema();

    /** Logger. */
    private final Log _log = LogFactory.getLog(getClass());

    /**
     * For elements with COBOL annotations produce a TOS schema column.
     * 
     * @param schema the COBOL-annotated XML schema
     * @param xsdElement the XML schema element
     * @param level the current depth
     * @param label to use as the column name for this element
     * @throws XsdMappingException if TOS schema column cannot be produced
     */
    public void processElement(final XmlSchema schema,
            final XmlSchemaElement xsdElement, final int level,
            final String label) throws XsdMappingException {

        XmlSchemaAnnotation annotation = xsdElement.getAnnotation();
        if (annotation != null && annotation.getItems().getCount() > 0) {
            XmlSchemaAppInfo appinfo = (XmlSchemaAppInfo) annotation.getItems()
                    .getItem(0);
            if (appinfo.getMarkup() != null) {
                for (int i = 0; i < appinfo.getMarkup().getLength(); i++) {
                    Node node = appinfo.getMarkup().item(i);
                    if (node instanceof Element
                            && node.getLocalName().equals(CobolMarkup.ELEMENT)
                            && node.getNamespaceURI().equals(CobolMarkup.NS)) {
                        writeElement(xsdElement, (Element) node, level, label);
                    }
                }
            }
        }

    }

    /**
     * Create a TOS schema column using the XML schema and COBOL annotations.
     * <p/>
     * Only elementary items translate to column, effectively resulting in
     * flattening the COBOL structure.
     * <p/>
     * Arrays are expanded into the maximum number of items. We are assuming TOS
     * schemas are not dynamic and don't support arrays hence there is no way we
     * can support DEPENDING ON.
     * 
     * @param xsdElement the XML schema element
     * @param elc the COBOL annotations as a set of DOM attributes
     * @param level the depth in the hierarchy
     * @param label to use as the column name for this element
     */
    protected void writeElement(final XmlSchemaElement xsdElement,
            final Element elc, final int level, final String label) {

        if (xsdElement.getSchemaType() instanceof XmlSchemaSimpleType) {

            XmlSchemaSimpleType xsdType = (XmlSchemaSimpleType) xsdElement
                    .getSchemaType();

            TosSchemaColumn tosColumn = new TosSchemaColumn();
            tosColumn.setLabel(label);
            tosColumn.setTalendType(getTalendType((xsdType)));

            String fractionDigits = elc
                    .getAttribute(CobolMarkup.FRACTION_DIGITS);
            if (fractionDigits != null && fractionDigits.length() > 0) {
                tosColumn.setPrecision(Integer.parseInt(fractionDigits));
            }

            /* Unsure about following attributes */
            tosColumn.setOriginalDbColumnName(label);
            tosColumn.setNullable(true);
            tosColumn.setKey("false");

            addColumn(xsdElement, tosColumn);

            if (_log.isDebugEnabled()) {
                _log.debug("Produced: " + tosColumn.toString());
            }
        }
    }

    /**
     * We might need to create multiple columns from an element. This is needed
     * because TOS schemas do not support arrays so every occurrence of an array
     * becomes a separate column.
     * <p/>
     * Replicated columns will have an incremental suffix that represent their
     * position within the array.
     * <p/>
     * Elements are arrays either because they have a maxOccurs greater than one
     * or because they are children of a complex element which was an array.
     * <p/>
     * There might be more than one array in an element hierarchy. So we might
     * need to insert multiple subscript.
     * 
     * @param xsdElement a simple element
     * @param tosColumn the associated TOS column
     */
    public void addColumn(final XmlSchemaElement xsdElement,
            final TosSchemaColumn tosColumn) {
        _tosSchema.addSchemaColumn(tosColumn);
    }

    /**
     * Recursive prefixing that will go down a hierarchy of arrays and produce
     * items
     * 
     * @param suffix the suffix so far
     * @param depth the current depth in the arrays hierarchy
     * @param dimensions the dimensions for each array from top to bottom
     * @param tosColumn the model of a column to add for all items
     */
    protected void cloneColumn(final String suffix, final int depth,
            final Long[] dimensions, final TosSchemaColumn tosColumn) {

        int newDepth = depth + 1;
        for (int i = 0; i < dimensions[depth]; i++) {
            String newSuffix = suffix + "_" + Integer.toString(i);
            if (newDepth == dimensions.length) {
                TosSchemaColumn itemColumn = tosColumn.clone();
                itemColumn.setLabel(itemColumn.getLabel() + newSuffix);
                _tosSchema.addSchemaColumn(itemColumn);
            } else {
                cloneColumn(newSuffix, newDepth, dimensions, tosColumn);
            }
        }
    }

    /**
     * @return the TOS schema produced
     */
    public TosSchema getTosSchema() {
        return _tosSchema;
    }

    /**
     * Derive a Talend Type from the XSD Type.
     * <p/>
     * This originates from:
     * <ul>
     * <li>http://en.wikipedia.org/wiki/Java_Architecture_for_XML_Binding</li>
     * <li>org.talend.core.mapping_XSD.xml</li>
     * </ul>
     * The list is not exhaustive and is limited to the types cob2xsd is capable
     * of producing.
     * 
     * 
     * @param xsdType a Talend Type or null if none can be derived
     * @return
     */
    public String getTalendType(final XmlSchemaSimpleType xsdType) {
        XmlSchemaSimpleTypeRestriction restriction = (XmlSchemaSimpleTypeRestriction) xsdType
                .getContent();
        if (restriction != null && restriction.getBaseTypeName() != null) {
            QName xsdTypeName = restriction.getBaseTypeName();
            if (xsdTypeName.equals(Constants.XSD_STRING)) {
                return "id_String";
            } else if (xsdTypeName.equals(Constants.XSD_INT)) {
                return "id_Integer";
            } else if (xsdTypeName.equals(Constants.XSD_LONG)) {
                return "id_Long";
            } else if (xsdTypeName.equals(Constants.XSD_SHORT)) {
                return "id_Short";
            } else if (xsdTypeName.equals(Constants.XSD_DECIMAL)) {
                return "id_BigDecimal";
            } else if (xsdTypeName.equals(Constants.XSD_FLOAT)) {
                return "id_Float";
            } else if (xsdTypeName.equals(Constants.XSD_DOUBLE)) {
                return "id_Double";
            } else if (xsdTypeName.equals(Constants.XSD_HEXBIN)) {
                return "id_byte[]";
            } else if (xsdTypeName.equals(Constants.XSD_UNSIGNEDINT)) {
                return "id_Long";
            } else if (xsdTypeName.equals(Constants.XSD_UNSIGNEDSHORT)) {
                return "id_Integer";
            } else if (xsdTypeName.equals(Constants.XSD_UNSIGNEDLONG)) {
                return "id_Long";
            } else {
                _log.warn("Unable to derive TOS type from "
                        + xsdTypeName.getLocalPart());
            }

        } else {
            _log.warn("Simple type without restriction " + xsdType.getName());
        }
        return null;

    }

    /**
     * Process a complex type.
     * 
     * @throws XsdMappingException
     * @param schema the COBOL-annotated XML schema
     * @param xsdComplexType the XML schema complex type
     * @param level the current depth
     * @param prefix a prefix to prepend to element names
     * @param suffix a suffix to append to element names
     * @throws XsdMappingException if TOS schema column cannot be produced
     */
    public void processComplexType(XmlSchema schema,
            XmlSchemaComplexType xsdComplexType, int level,
            final String prefix, final String suffix)
            throws XsdMappingException {

    }

    /** {@inheritDoc} */
    public void setUp() throws IOException {
    }

}
